Skip to content

feat: apply OEP-66 queryset-scoping pattern to Enrollments v2 API - #39037

Merged
Faraz32123 merged 3 commits into
openedx:masterfrom
edly-io:feat/apply-queryset-scoping-to-enrollments-v2
Sep 17, 2026
Merged

Faraz32123 merged 3 commits into
openedx:masterfrom
edly-io:feat/apply-queryset-scoping-to-enrollments-v2

Conversation

@Abdul-Muqadim-Arbisoft

Copy link
Copy Markdown
Contributor

Adopt the OEP-66 "Separating Authorization Concerns in List Endpoints" record-visibility layer on the admin enrollment list, using the shared building blocks now published in edx-drf-extensions.

EnrollmentsAdminListView is an ORM-backed ListAPIView, so it wires the three authorization concerns separately:

  • Endpoint access: permission_classes = (IsAdminUser,).
  • Record visibility: ScopedQuerysetMixin applies AdminEnrollmentScopingPolicy to the base queryset in get_queryset(). The policy is an intentional pass-through for platform admins today, documented as the single seam where an openedx-authz scope-set filter (e.g. restricting a delegated, org-scoped admin to their organization's enrollments) will plug in later; behavior is unchanged.
  • User-driven filtering: the form-based course_key/course_keys/username/ email/ordering filtering moves from get_queryset() into filter_queryset(), so it runs after scoping and only narrows the already-authorized queryset.

Bumps edx-drf-extensions 10.6.0 -> 10.7.0, the release that adds the reusable ScopingPolicy (a typing.Protocol) and ScopedQuerysetMixin. Adds TestEnrollmentsAdminListView regression tests covering endpoint access (401/403), pass-through scoping (admin sees all rows), the course_key/username filters, the 400-on-invalid-params path, the ADR 0033 Deprecation header, and the scoping-policy pass-through.

Follows up the closed PR #38847: per review, the shared ScopingPolicy / ScopedQuerysetMixin tooling moved to edx-drf-extensions (#569) as a subject-based typing.Protocol with a duck-typed mixin check, and this change consumes it rather than defining it locally.

@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft requested a review from a team as a code owner August 27, 2026 13:28
@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft force-pushed the feat/apply-queryset-scoping-to-enrollments-v2 branch from 78d3f46 to 3678f4c Compare August 30, 2026 10:04
def test_filter_by_username_narrows(self):
"""User-driven filter (filter_queryset) still narrows by username."""
self.client.force_authenticate(user=self.admin)
response = self.client.get(self.url, {"username": self.learner_b.username})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only work for admins correct? we should have the inverted test validating that users can't see eachothers enrollments by passing eachothers names in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup that should be admin only. Just added test_non_admin_cannot_list_another_users_enrollments for it

Comment thread openedx/core/djangoapps/enrollments/v2/views.py Outdated

def scope(self, queryset, subject):
# Platform admins (IsAdminUser) see all enrollments; nothing to narrow yet.
return queryset

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of boilerplate for essentially a no-op on scoping, can we simplify this? Maybe we have some sort of a "FullScopePolicy" that can be used in many places for things like this?

@Abdul-Muqadim-Arbisoft Abdul-Muqadim-Arbisoft Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think FullScopePolicy would be a good to add since any admin only list that adopts the scoping layer would otherwise end up hand writing the same pass through class, and since its a reusable DRF tooling it should be placed next to ScopingPolicy / ScopedQuerysetMixin so plugins can use it too.

I've opened openedx/edx-drf-extensions#578 adding it there, and this PR now imports it from the library. CI here will stay red until that's released; once you approve, merge and publish 10.9.0, I'll bump the pin in this PR and it should be good to merge.

Comment thread openedx/core/djangoapps/enrollments/v2/views.py Outdated
@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft force-pushed the feat/apply-queryset-scoping-to-enrollments-v2 branch from 3678f4c to b106b61 Compare September 12, 2026 15:07
@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft force-pushed the feat/apply-queryset-scoping-to-enrollments-v2 branch from b106b61 to 3ca8acd Compare September 14, 2026 16:12
Abdul-Muqadim-Arbisoft added a commit to edly-io/edx-platform that referenced this pull request Sep 14, 2026
Review feedback on openedx#39037: the code should follow the standards without naming
the documents. The three authoring urls.py modules now read "Authoring API vN
URLs.", the URL-structure test banners and the Enrollment v2 module docstring
lose their rule references, and the remaining comments say what the code does
instead — deprecation window rather than OEP-21 window, "whose course_key path
converter hands views a parsed key" rather than a rule number. Only lines this
branch adds are touched; the pre-existing ADR references elsewhere in these
files are left alone, the one exception being the ADR 0028 line in the
Enrollment v2 module docstring, which this branch was already rewriting.

Prose only apart from one assert message in the new URL-structure test, which
now reads "missing error-envelope field". ruff passes.

@feanil feanil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, once this can be rebased on the published version of openedx/edx-drf-extensions#578

@feanil

feanil commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

The drf-extensions PR has been merged and published, can you update this to pull that in?

Wire EnrollmentsAdminListView through ScopedQuerysetMixin from
edx-drf-extensions so endpoint access, record visibility and user-driven
filtering are kept separate. Platform admins may see every enrollment, so
the view uses the library's FullScopePolicy for now; a narrower policy can
replace it later without touching the view. The form-based filtering moves
from get_queryset() into filter_queryset(), so it only narrows the scoped
queryset. No behavior change.

Adds regression tests for the admin list: 401/403, a regular user filtering
by someone else's username gets 403 with no data, admins see all rows, the
course_key/username filters, the 400 on invalid params, and the ADR 0033
Deprecation header.

Bumps edx-drf-extensions 10.8.0 -> 10.9.0, the release that adds
FullScopePolicy (edx-drf-extensions#578). Follows up
openedx#38847, whose shared scoping tooling moved to edx-drf-extensions in #569.
@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft force-pushed the feat/apply-queryset-scoping-to-enrollments-v2 branch from 3ca8acd to a153a0f Compare September 16, 2026 16:32
@Faraz32123
Faraz32123 merged commit 9ae7994 into openedx:master Sep 17, 2026
42 checks passed
Abdul-Muqadim-Arbisoft added a commit to edly-io/edx-platform that referenced this pull request Sep 20, 2026
Resolves conflicts with openedx#39037 (queryset scoping) and the edx-drf-extensions
10.9.0 bump on master. Keeps both new Enrollment v2 test classes; takes
master's requirements and uv.lock, which supersede this branch's 10.8.0 bump
so the branch no longer changes any requirements file.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants